#region Imported Namespaces
using System;
using System.Collections;
using System.Data;
using BO = PHSRAG.Insight21.BusinessObjects;
using Utility = PHSRAG.Utility;
using ICachePolicy = PHSRAG.Insight21.Policy.ICachePolicy;
using DataAccess = PHSRAG.Utility.DataAccess;
using GenerateKey = PHSRAG.Insight21.BusinessObjects.ModuleKey;
#endregion

namespace PHSRAG.Insight21.FormsManagement
{
	#region Class Answer
	/// <summary>
	/// An object of Answer class represents an answer to a question (QuestionnaireItem) supplied by the user.
	/// 
	/// The 'name' instance variable holds the name of the question (QuestionnaireItem) and 'answerValue' holds the answer. 
	/// The answer could be any text entered by the user or the selected value for the predefined options provided to the user.
	/// 
	/// Answers to child questions are embedded within the parent answer. 
	/// </summary>
	public class Answer : BO.SmartBusinessObject,System.ICloneable
	{
		#region instance variables
		private int			questionnaireAnswerId;
		private string		name;
		private string		answerValue;
		private bool		groupIndicator;
		private int			parentAnswerId;
		private ArrayList	childAnswers;
		#endregion
		
		#region Constants
		private static string AnswerKey = GenerateKey.MakeKey("Answer_79D95A8F-E3D4-47b5-A428-9894DFDCEE3B",GenerateKey.Humans);
		private const string ColumnId = "Id";
		private const string ColumnName = "Name";
		private const string ColumnQuestionnaireAnswerId = "QuestionnaireAnswerId";
		private const string ColumnValue = "Value";
		private const string ColumnGroupIndicator = "GroupIndicator";
		private const string ColumnParentAnswerId = "ParentAnswerId";
		#endregion

		#region Properties
		/// <summary>
		/// Cache Key uniquely identifies this object within the cache.
		/// </summary>
		/// <returns>the unique string for this object.</returns>
		public override string CacheKey()
		{
			return AnswerKey;
		}

		/// <summary>
		/// The Name of the question that this answer relates to.
		/// </summary>
		public string Name 
		{
			get { return name; }
			set { SetDirty( ref name, value ); }
		}

		/// <summary>
		/// The QuestionnaireAnswer this Answer belongs to.
		/// </summary>
		public int QuestionnaireAnswerId
		{
			get { return questionnaireAnswerId; }
			set { SetDirty(ref questionnaireAnswerId, value); }
		}

		/// <summary>
		/// Answer to the question!
		/// </summary>
		public string Value
		{
			get { return answerValue; }
			set { SetDirty( ref answerValue, value ); }
		}

		/// <summary>
		/// determines if an answer is part of a group. If it is,
		/// this flag will be set to true, and the answer will have a 
		/// parent answer ID filled in.
		/// </summary>
		public bool GroupIndicator
		{
			get { return groupIndicator; }
			set { SetDirty( ref groupIndicator, value ); }
		}

		/// <summary>
		/// The optional ID of a parent answer. This occurs when an answer 
		/// is nested and dependent on another answer (see XRay question as
		/// an example).
		/// </summary>
		public int ParentAnswerId 
		{
			get { return parentAnswerId; }
			set { SetDirty( ref parentAnswerId, value ); }
		}

		/// <summary>
		/// Gets or sets the list holding the child answers. If there are no dependant questions 
		/// for the question pertaining to this answer object, then ChildAnswers will return null
		/// </summary>
		public ArrayList ChildAnswers
		{
			get { return childAnswers; }
			set { childAnswers = value;	}
		}
		#endregion

		#region Constructors
		/// <summary>
		/// constructor called when creating a new Answer.
		/// </summary>
		/// <param name="cachePolicy">The prevalent cache policy</param>
		public Answer(ICachePolicy cachePolicy) : base(cachePolicy)
		{
		}

		/// <summary>
		/// Create the object based on the passed in row.
		/// </summary>
		/// <param name="cachePolicy">The prevalent cache policy</param>
		/// <param name="row">DataRow object holding the values for instance variables</param>
		public Answer(ICachePolicy cachePolicy, DataRow row) : base(cachePolicy)
		{
			PopulateDataMembers(row);
		}
		#endregion

		#region Overriden methods
		/// <summary>
		/// Deletes the answer including answers of all the child questions.
		/// </summary>
		/// <param name="sqlTransaction">The SQL transaction to be used while executing the delete</param>
		protected override void DeleteObject(System.Data.SqlClient.SqlTransaction sqlTransaction)
		{
			try
			{
				// delete the contained answers first
				if (childAnswers != null)
				{
					foreach (Answer answer in childAnswers )
					{
						answer.Delete(sqlTransaction );
					}
				}

				DataAccess.ExecuteNonQuery( sqlTransaction.Connection, sqlTransaction, "DeleteAnswer", 
					DataAccess.BuildSqlParameterArray( "@id", SqlDbType.Int, Id ) );
			}
			catch(Exception ex)
			{
				Utility.ExceptionReport.WriteToEventLog("Answer.DeleteObject: Failed to delete the Answer object", ex, true, Utility.ExceptionReport.ReportType.Error);
				throw new Exception("Answer.DeleteObject: Failed to delete the Answer object", ex);
			}
		}

		
		/// <summary>
		///  Determines if the object is dirty based on whether or not atleast one of its properties have changed or any of its contained objects have changed
		/// </summary>
		/// <returns>true - if the object is modified</returns>
		public override bool IsDirty()
		{
			if (dirty)
				return true;
			if (childAnswers != null)
			{
				foreach(Answer o in childAnswers)
				{
					if (o.IsDirty())
						return true;
				}
			}
			return false;
		}

		
		/// <summary>
		/// Load the object based on an Id. Will throw an exception if args.count != 1
		/// </summary>
		/// <param name="connectionString">DB connection string.</param>
		/// <param name="args">Id of the Answer to be loaded</param>
		public override void Load(string connectionString, params object[] args)
		{
			if ( args.Length != 1 )
				throw new Exception("Answer.Load expects ID only.");

			try
			{
				DataSet newDataSet = Utility.DataAccess.GetDataSet( connectionString, "GetAnswer", 
					Utility.DataAccess.BuildSqlParameterArray("@id", SqlDbType.Int, (int)args[0]));
				if (newDataSet.Tables.Count > 0)
				{
					DataRowCollection rows = newDataSet.Tables["Answer"].Rows;
					if (rows.Count > 0)
					{
						PopulateDataMembers(rows[0]);
					}
				}
				else
				{
					throw new Exception("GetAnswer stored procedure returned incorrect results");
				}
			}
			catch(Exception ex)
			{
				Utility.ExceptionReport.WriteToEventLog ("Answer.Load(): Failed to load the answer with the specified id", ex, true, Utility.ExceptionReport.ReportType.Error);
				throw new Exception("Answer.Load(): Failed to load the answer with the specified id", ex);
			}
		}

		
		/// <summary>
		/// Save the Answer object. Note that there are currently no other objects contained by
		/// an instance of an Answer, so no other saves need be called here.
		/// </summary>
		/// <param name="sqlTransaction">The SQL transaction to be used while executing the save</param>
		protected override void SaveObject(System.Data.SqlClient.SqlTransaction sqlTransaction)
		{
			Id = Convert.ToInt32(DataAccess.ExecuteScalar( sqlTransaction.Connection, sqlTransaction, "SaveAnswer", 
				DataAccess.BuildSqlParameterArray(
				"@id", SqlDbType.Int, Id,
				"@questionnaireAnswerId", SqlDbType.Int, questionnaireAnswerId,
				"@name", SqlDbType.VarChar, name,
				"@value", SqlDbType.VarChar, answerValue,
				"@groupIndicator", SqlDbType.Bit, groupIndicator,
				"@parentAnswerId", SqlDbType.Int, (parentAnswerId == 0) ? DBNull.Value : (object)parentAnswerId )));

			if (childAnswers != null)
			{
				//Saving Answer objects
				//ChildAnswers could be marked as deleted. The following logic will call Delete on objects marked for deletion
				//and remove them from the collection and as such a foreach loop is not used. The variable 'index' is a pointer
				//to the object in the collection that is being processed. The 'count' keeps track of the number of objects in 
				//the collection. The count will change whenever there is an object that is marked for deletion.
				int index = 0;
				int count = childAnswers.Count;
				while ( index < count)
				{
					Answer answer = (Answer)childAnswers[index];
					answer.ParentAnswerId = Id;
					answer.QuestionnaireAnswerId = questionnaireAnswerId;
					answer.Save(sqlTransaction);
					if (!answer.MarkForDelete)
					{
						++index;
					}
					else
					{
						childAnswers.RemoveAt(index);
						--count;
					}
				}
			}

		}

		
		/// <summary>
		/// Cache this instance using the prevalent cache policy.
		/// </summary>
		public override void Cache()
		{
			CachePolicy.Cache(AnswerKey, this);
		}

		
		/// <summary>
		/// Remove this instance from the cache using the prevalent cache policy.
		/// </summary>
		public override void UnCache()
		{
			CachePolicy.UnCache(AnswerKey);
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Loads all answers hierarchically(childanswers within parent answers) for a given QuestionnaireAnswer Id. 
		/// This method builds all the top level answers first(those which do not have any ParentAnswerId) and then recursively loops through its children
		/// </summary>
		/// <param name="questionnaireAnswerId">The QuestionnaireAnswerId for which the answers have to be fetched</param>
		/// <param name="CachePolicy">The prevalent cache policy</param>
		/// <returns>A collection of Answer objects</returns>
		public static ArrayList GetAnswers ( int questionnaireAnswerId, ICachePolicy policy )
		{
			ArrayList answers = new ArrayList();

			DataSet answersDataSet = Utility.DataAccess.GetDataSet( Utility.DataAccess.ConnectionString,
				"GetQuestionnaireAnswerAnswers",
				Utility.DataAccess.BuildSqlParameterArray
				("@questionnaireAnswerId", SqlDbType.Int, questionnaireAnswerId));

			DataTable answersTable = answersDataSet.Tables[0];
			//for each top level answer (answer with ParentAnswerId as null or 0)
			foreach(DataRow row in answersTable.Select("IsNull(ParentAnswerId, 0) = 0"))
			{
				Answer answer = new Answer(policy, row);
				answers.Add(answer);
				AddChildAnswers(answer, policy, answersTable);
			}

			return answers;
		}


		/// <summary>
		/// Clones the answer object but sets the id and the questionnaireAnswerId to 0.
		/// This method is to overcome the weakly typed nature of the ICloneable.Clone() method.
		/// </summary>
		/// <returns>Answer clone</returns>
		public Answer Clone()
		{
			Answer answerClone = (Answer)MemberwiseClone();
			answerClone.Dirty = true;
			// since memberwise clone just copies the reference to the child answers,
			// we need to create clones of them as well, and give them as child answers
			// to the new answer
			if (childAnswers != null)
			{
				answerClone.ChildAnswers = new ArrayList();
				foreach (Answer childAnswer in childAnswers)
				{
					Answer clonedChildAnswer = childAnswer.Clone();
					clonedChildAnswer.parentAnswerId = 0;
					clonedChildAnswer.questionnaireAnswerId = 0;
					answerClone.ChildAnswers.Add(clonedChildAnswer);
				}
			}
			answerClone.Id=0;
			answerClone.QuestionnaireAnswerId=0;
			return answerClone;
		}

		#endregion

		#region Private methods
		/// <summary>
		/// Creates answer objects for dependant(child) questions and adds them to the parent Answer object
		/// </summary>
		/// <param name="parentAnswer">The answer for the parent question whose child question answers have to be constructed</param>
		/// <param name="policy">The prevalent cache policy</param>
		/// <param name="answersTable">Table holding all the answers' information</param>
		private static void AddChildAnswers(Answer parentAnswer, ICachePolicy policy, DataTable answersTable)
		{
			DataRow[] rows = answersTable.Select(string.Format("ParentAnswerId = {0}", parentAnswer.Id));
			if (rows.Length > 0 && parentAnswer.childAnswers == null)
				parentAnswer.childAnswers = new ArrayList();
			foreach(DataRow row in rows)
			{
				Answer answer = new Answer(policy, row);
				parentAnswer.childAnswers.Add(answer);
				AddChildAnswers(answer, policy, answersTable);
			}
		}
		
		
		/// <summary>
		/// Populates instance fields with corresponding values in the datarow
		/// </summary>
		/// <param name="row">DataRow holding the values to be assigned to instance variables</param>
		private void PopulateDataMembers(DataRow row)
		{
			Id			= (int)row[ColumnId];
			questionnaireAnswerId = (int)row[ColumnQuestionnaireAnswerId];
			name		= (string)row[ColumnName];
			object o	= row[ColumnValue];
			answerValue = (o == DBNull.Value) ? "" : (string)o;
			groupIndicator = (bool)row[ColumnGroupIndicator];
			o			= row[ColumnParentAnswerId];		
			parentAnswerId = (o == DBNull.Value) ? 0 : (int)o;
		}
		#endregion

		#region IClonable member
		/// <summary>
		/// Explicit interface method implementation - available for 
		/// clients of ICloneable, but invisible to other
		/// clients of Answer class
		/// </summary>
		/// <returns>Answer clone as a generic object</returns>
		object ICloneable.Clone()
		{
			return Clone();
		}
		#endregion
	}
	#endregion
}